package com.lambkit.db;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import com.lambkit.db.hutool.RowDataHandler;

import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

/**
 * @author yangyong(孤竹行)
 */
public class ResultSetBuilder {

    public static ResultSetBuilder of() {
        return new ResultSetBuilder();
    }

    public <T extends IRowData<T>> List<T> buildList(ResultSet rs, boolean withMetaInfo, Class<T> clazz) throws SQLException {
        final ResultSetMetaData meta = rs.getMetaData();
        final int columnCount = meta.getColumnCount();
        List<T> collection = new ArrayList<>();
        while (rs.next()) {
            T row = ReflectUtil.newInstance(clazz);
            collection.add(handleRow(row, columnCount, meta, rs, withMetaInfo));
        }
        return collection;
    }

    public <T extends IRowData<T>> PageData<T> buildPage(ResultSet rs, boolean withMetaInfo, int pageNumber, int pageSize, int total, Class<T> clazz) throws SQLException {
        final ResultSetMetaData meta = rs.getMetaData();
        final int columnCount = meta.getColumnCount();
        List<T> collection = new ArrayList<>();
        while (rs.next()) {
            T row = ReflectUtil.newInstance(clazz);
            collection.add(handleRow(row, columnCount, meta, rs, withMetaInfo));
        }
        PageData<T> pageData = new PageData<>(pageNumber, pageNumber, total);
        pageData.setList(collection);
        return pageData;
    }

    public <T extends IRowData<T>> PageData<T> buildPage(ResultSet rs, boolean withMetaInfo, PageData<T> pageData, Class<T> clazz) throws SQLException {
        final ResultSetMetaData meta = rs.getMetaData();
        final int columnCount = meta.getColumnCount();
        List<T> collection = new ArrayList<>();
        while (rs.next()) {
            T row = ReflectUtil.newInstance(clazz);
            collection.add(handleRow(row, columnCount, meta, rs, withMetaInfo));
        }
        pageData.setList(collection);
        return pageData;
    }

    public <T extends IRowData<T>> T build(ResultSet rs, boolean withMetaInfo, Class<T> clazz) throws SQLException {
        final ResultSetMetaData meta = rs.getMetaData();
        final int columnCount = meta.getColumnCount();
        T row = ReflectUtil.newInstance(clazz);
        return rs.next() ? handleRow(row, columnCount, meta, rs, withMetaInfo) : null;
    }

    /**
     * 处理单条数据
     * @param row RowData对象
     * @param columnCount 列数
     * @param meta ResultSetMetaData
     * @param rs 数据集
     * @param withMetaInfo 是否包含表名、字段名等元信息
     * @return 每一行的Entity
     * @throws SQLException SQL执行异常
     * @since 3.3.1
     */
    public <T extends IRowData<T>> T handleRow(T row, int columnCount, ResultSetMetaData meta, ResultSet rs, boolean withMetaInfo) throws SQLException {
        int type;
        String columnLabel;
        List<String> columnNames = CollUtil.newArrayList();
        for (int i = 1; i <= columnCount; i++) {
            type = meta.getColumnType(i);
            columnLabel = meta.getColumnLabel(i);
            if("rownum_".equalsIgnoreCase(columnLabel)){
                // issue#2618@Github
                // 分页时会查出rownum字段，此处忽略掉读取
                continue;
            }
            row.set(columnLabel, getColumnValue(rs, i, type, null));
            columnNames.add(columnLabel);
        }
        if (withMetaInfo) {
            try {
                row.setTableName(meta.getTableName(1));
            } catch (SQLException ignore){
                //issue#I2AGLU@Gitee
                // Hive等NoSQL中无表的概念，此处报错，跳过。
            }
            row.setColumnNames(columnNames);
        }
        return row;
    }

    // -------------------------------------------------------------------------------------------------------------- Private method start
    /**
     * 获取字段值<br>
     * 针对日期时间等做单独处理判断
     *
     * @param rs {@link ResultSet}
     * @param columnIndex 字段索引
     * @param type 字段类型，默认Object
     * @param targetColumnType 结果要求的类型，需进行二次转换（null或者Object不转换）
     * @return 字段值
     * @throws SQLException SQL异常
     */
    private Object getColumnValue(ResultSet rs, int columnIndex, int type, Type targetColumnType) throws SQLException {
        Object rawValue = null;
        switch (type) {
            case Types.TIMESTAMP:
                try{
                    rawValue = rs.getTimestamp(columnIndex);
                } catch (SQLException ignore){
                    // issue#776@Github
                    // 当数据库中日期为0000-00-00 00:00:00报错，转为null
                }
                break;
            case Types.TIME:
                rawValue = rs.getTime(columnIndex);
                break;
            default:
                rawValue = rs.getObject(columnIndex);
        }
        if (null == targetColumnType || Object.class == targetColumnType) {
            // 无需转换
            return rawValue;
        } else {
            // 按照返回值要求转换
            return Convert.convert(targetColumnType, rawValue);
        }
    }
}
